/**
 * @file mmc.c
 * @brief memory manage controller
 * @details
 *  > * Manage space of random access memory both of internal and external;
 *  > * Alloc space dynamically;
 *  > * Recovery the alloced memory;
 * @author Qilong Huang(huangqilong@memsensing.com)
 * @date 2022-4-3
 * @version 1.0
 * @copyright Copyright (c)
 * All rights reserved.
 * *************************************************
 * @par Change log:
 * <table>
 * <tr><th>Date <th>Version  <th>Author <th>Description
 * <tr><td>2018/08/17   <td>1.0 <td>Qilong Huang  <td>Initial
 * </table>
 *
 *********************************************************
 */
#include "mmc.h"

static uint8_t exSdramHeap[MMC_EXSDRAM_MAX_AS_HEAP_SIZE] __attribute__((section(".exsdram")));

mmc_Block_t *pHeapStartBlock = NULL, *pHeapEndBlock = NULL, *pHeapMayUseBlock = NULL;
size_t VaildTotaLSize;

static void mmc_HeapInit(void);
static void mmc_MemDefrag(void);
static void mmc_ContinousFreeMemoryDefrag(mmc_Block_t *seekMemory);

/**
 * @name static HeapInit(void)
 * @brief Init the heap pointer and mark the alloced range in sdram \n
 * @param none
 * @return none
 */
static void mmc_HeapInit(void)
{
    volatile size_t uxAddress;
    uxAddress = (size_t)&exSdramHeap[0];
    mmc_Block_t *pAlignedHeapBlock = NULL;
    size_t xTotalHeapSize = MMC_EXSDRAM_MAX_AS_HEAP_SIZE;

    if ((uxAddress & MMC_BYTE_ALIGNMENT_MASK) != 0)
    {
        uxAddress &= ~MMC_BYTE_ALIGNMENT_MASK;
        uxAddress += MMC_BYTE_ALIGNMENT;
        xTotalHeapSize -= (uxAddress - (size_t)&exSdramHeap[0]);
    }
    pAlignedHeapBlock = (mmc_Block_t *)uxAddress;
    pHeapStartBlock = pAlignedHeapBlock;

    uxAddress = (size_t)pAlignedHeapBlock + xTotalHeapSize - sizeof(mmc_Block_t);
    uxAddress &= ~MMC_BYTE_ALIGNMENT_MASK;
    pHeapEndBlock = (mmc_Block_t *)uxAddress;

    pHeapStartBlock->BlockStatus = (MMC_BLOCK_STATUS_EMPTY_FLAG | MMC_BLOCK_STATUS_START_BLOCK_FLAG);
    pHeapStartBlock->BlockSize = (size_t)pHeapEndBlock - (size_t)pHeapStartBlock;
    pHeapStartBlock->NextBlock = pHeapEndBlock;
    pHeapStartBlock->VaildSpaceStart = (uint8_t *)((size_t)pHeapStartBlock + (size_t)sizeof(mmc_Block_t));

    pHeapMayUseBlock = pHeapStartBlock;
    VaildTotaLSize = pHeapStartBlock->BlockSize;

    pHeapEndBlock->BlockStatus = (MMC_BLOCK_STATUS_END_BLOCK_FLAG);
    pHeapEndBlock->BlockSize = 0;
    pHeapEndBlock->NextBlock = NULL;
    pHeapEndBlock->VaildSpaceStart = NULL;
}

/**
 * @name void *MMC_Alloc(size_t xRequestSize)
 * @brief Alloc a continuous block memory according request size \n
 * @param xRequestSize : Rquest alloc size, xRequestSize bytes.
 * @return none
 */
void *MMC_Alloc(size_t xRequestSize)
{
    void *mmcReturn = NULL;
    if (pHeapEndBlock == NULL)
    {
        mmc_HeapInit();
    }
    if (xRequestSize > 0 && xRequestSize < VaildTotaLSize)
    {
        mmc_Block_t *allocedMemory = NULL;
        allocedMemory = pHeapStartBlock;
        while (allocedMemory->NextBlock != NULL)
        {
            if ((allocedMemory->BlockStatus & MMC_BLOCK_STATUS_EMPTY_FLAG) && allocedMemory->BlockSize >= xRequestSize)
            {
                if ((xRequestSize & MMC_BYTE_ALIGNMENT_MASK) != 0)
                {
                    xRequestSize &= ~MMC_BYTE_ALIGNMENT_MASK;
                    xRequestSize += MMC_BYTE_ALIGNMENT;
                }

                allocedMemory->BlockStatus &= (~MMC_BLOCK_STATUS_EMPTY_FLAG);
                size_t tmpSize = allocedMemory->BlockSize;
                allocedMemory->BlockSize = (size_t)xRequestSize;
                allocedMemory->VaildSpaceStart = (uint8_t *)((size_t)allocedMemory + (size_t)sizeof(mmc_Block_t));
                mmc_Block_t *tempNode = allocedMemory->NextBlock;
                if (tmpSize - allocedMemory->BlockSize > (size_t)sizeof(mmc_Block_t))
                {
                    //Create the new node
                    mmc_Block_t *newMemoryBlock = NULL;
                    newMemoryBlock = (mmc_Block_t *)(allocedMemory->VaildSpaceStart + allocedMemory->BlockSize);
                    allocedMemory->NextBlock = newMemoryBlock;
                    newMemoryBlock->BlockSize = tmpSize - allocedMemory->BlockSize - (size_t)sizeof(mmc_Block_t);
                    newMemoryBlock->BlockStatus = MMC_BLOCK_STATUS_EMPTY_FLAG;
                    newMemoryBlock->VaildSpaceStart = (uint8_t *)((size_t)newMemoryBlock + (size_t)sizeof(mmc_Block_t));
                    newMemoryBlock->NextBlock = tempNode;
                }

                uint8_t *mmCleanPt = allocedMemory->VaildSpaceStart;
                for (size_t i = 0; i < allocedMemory->BlockSize; i++)
                {
                    *mmCleanPt++ = 0x00;
                }

                pHeapMayUseBlock = allocedMemory->NextBlock;
                VaildTotaLSize -= ((size_t)allocedMemory->BlockSize + (size_t)sizeof(mmc_Block_t));
                mmcReturn = allocedMemory->VaildSpaceStart;
                break;
            }
            else
            {
                allocedMemory = allocedMemory->NextBlock;
            }
        }
    }
    return mmcReturn;
}

/**
 * @name void MMC_Free(void *pFreeAddress)
 * @brief Free alloced memory \n
 * @param pFreeAddress : The memory address be relased.
 * @return none
 */
void MMC_Free(void *pFreeAddress)
{
    if (pFreeAddress != NULL)
    {
        mmc_Block_t *freedMemory = NULL;
        freedMemory = (mmc_Block_t *)((size_t)pFreeAddress - sizeof(mmc_Block_t));
        freedMemory->BlockStatus = MMC_BLOCK_STATUS_EMPTY_FLAG;
        uint8_t *mmCleanPt = freedMemory->VaildSpaceStart;
        for (size_t i = 0; i < freedMemory->BlockSize; i++)
        {
            *mmCleanPt++ = 0x00;
        }
        VaildTotaLSize += ((size_t)freedMemory->BlockSize + (size_t)sizeof(mmc_Block_t));
        pFreeAddress = NULL;
        mmc_MemDefrag();
    }
}

/**
 * @name static void mmc_MemDefrag(void)
 * @brief Merge adjacent empty memory block\n
 * @param none
 * @return none
 */
static void mmc_MemDefrag(void)
{
    mmc_Block_t *seekMemory = NULL;
    seekMemory = pHeapStartBlock;
    while (seekMemory != NULL)
    {
        if (seekMemory->BlockStatus & MMC_BLOCK_STATUS_EMPTY_FLAG)
        {
            mmc_ContinousFreeMemoryDefrag(seekMemory);
        }
        seekMemory = seekMemory->NextBlock;
    }
}

/**
 * @name static void mmc_ContinousFreeMemoryDefrag(mmc_Block_t *seekMemory)
 * @brief Seek and merge adjacent empty memory block\n
 * @param seekMemory : Seeked start memory.
 * @return none
 */
static void mmc_ContinousFreeMemoryDefrag(mmc_Block_t *seekMemory)
{
    mmc_Block_t *seekNextMemory = seekMemory->NextBlock;
    if (seekNextMemory->BlockStatus & MMC_BLOCK_STATUS_EMPTY_FLAG)
    {
        seekMemory->BlockStatus = MMC_BLOCK_STATUS_EMPTY_FLAG;
        seekMemory->BlockSize = seekMemory->BlockSize + seekNextMemory->BlockSize + sizeof(mmc_Block_t);
        seekMemory->NextBlock = seekNextMemory->NextBlock;
        uint8_t *mmCleanPt = (uint8_t *)seekNextMemory;
        for (size_t i = 0; i < sizeof(mmc_Block_t); i++)
        {
            *mmCleanPt++ = 0x00;
        }
    }
}
